package com.suray.wcs.service.plc;

import com.suray.basic.wcs.path.core.Node;
import com.suray.basic.wcs.plc.core.Plc;
import com.suray.basic.wcs.plc.enums.Location;
import com.suray.basic.wcs.plc.pojo.DeviceBase;
import com.suray.basic.wcs.plc.pojo.oper.lifter.ForkLifterOper;
import com.suray.basic.wcs.plc.service.impl.LifterCodeDBServiceImpl;
import com.suray.basic.wcs.plc.utils.LifterCurSegmentInfo;
import com.suray.basic.wcs.plc.utils.LifterTaskProcess;
import com.suray.basic.wcs.utils.*;
import com.suray.commin.core.utils.SpringContextHolder;
import com.suray.wcs.service.core.util.LockNodeManager;
import com.suray.wcs.service.plc.util.LifterTaskStatus;
import com.suray.wcs.service.plc.util.impl.ForkLifterEvent;
import com.suray.wcs.service.system.service.impl.NodeDBServiceImpl;
import com.suray.wcs.service.util.WcsUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;

public class LifterTaskThread {
    private NodeDBServiceImpl nodeDBService = SpringContextHolder.getBean(NodeDBServiceImpl.class);
    private ForkLifterEvent lifterEvent = SpringContextHolder.getBean(ForkLifterEvent.class);
    private LifterCodeDBServiceImpl lifterCodeDBService = SpringContextHolder.getBean(LifterCodeDBServiceImpl.class);
    private Logger log = LoggerFactory.getLogger(this.getClass());

    private ForkLifterOper forkLifterOper;

    private int taskId;


    private LifterTaskStatus status;
    private LifterCurSegmentInfo curSegmentInfo;

    private Node startNode;
    private Node endNode;
    private boolean taskEnd;

    private int cmdNo;

    private Location startLocation;
    private Location endLocation;

    public LifterTaskThread(String name, int taskId, ForkLifterOper forkLifterOper,
                            int startLayer, Location startLocation, int endLayer, Location endLocation) {
        LoggerUtil.info(name + "开始执行plc任务：" + taskId, this.getClass());

        this.forkLifterOper = forkLifterOper;
        this.taskId = taskId;
        this.status = LifterTaskStatus.INIT;
        this.taskEnd = false;

        this.startLocation = startLocation;
        this.endLocation = endLocation;
        this.cmdNo = 1;

        startNode = new Node(forkLifterOper.getNode(startLayer, forkLifterOper.analyzeLocation(startLocation)));
        endNode = new Node(forkLifterOper.getNode(endLayer, forkLifterOper.analyzeLocation(endLocation)));

        forkLifterOper.lifterInterruptMap.put(forkLifterOper.getSeries(), false);

    }

    public void start() {
        Thread thread = new Thread(() -> {
            try {
                runTask();
            } catch (Throwable e) {
                LoggerUtil.error(forkLifterOper.getSeries() + "任务" + taskId + "--异常", this.getClass());
                WcsFuncUtil.exceptionLog(e, this.getClass());
            }
        });

        thread.setName(forkLifterOper.getDeviceName() + "任务" + taskId);
        thread.start();
    }

    private void runTask() {
        LoggerUtil.info(forkLifterOper.getSeries() + "任务" + taskId + "--任务开始", this.getClass());
        while (true) {
            try {
                process();
                if (taskEnd) {
                    break;
                }
                WcsFuncUtil.delay(1000);
            } catch (Exception e) {
                LoggerUtil.info(forkLifterOper.getSeries() + "任务" + taskId + "--任务异常", this.getClass());
                throw e;
            }
        }

        while (!forkLifterOper.isPermitClearTask()) {
            checkLifter();
            WcsFuncUtil.delay(100);
        }

        forkLifterOper.clearTask();
        forkLifterOper.setTaskDes(null);
        try {
            lifterEvent.setHoistFinalTaskNoClear(forkLifterOper);
            LoggerUtil.info(forkLifterOper + "任务" + taskId + "--任务结束", this.getClass());
        } catch (IOException e) {
            WcsUtil.notification(forkLifterOper.getSeries() + "任务" + taskId + "--清除完成任务编号 通讯失败");
            LoggerUtil.info(forkLifterOper.getSeries() + "任务" + taskId + "--清除完成任务编号 通讯失败", this.getClass());
        }
    }

    private void endThread() {
        taskEnd = true;
    }

    private void checkLifter() {
        Boolean bool = forkLifterOper.lifterInterruptMap.get(forkLifterOper.getSeries());
        if (bool != null && bool) {
            throw new RuntimeException(forkLifterOper.getDeviceName() + "任务中断");
        }
        if (forkLifterOper.isLifterFault()) {
            throw new RuntimeException(forkLifterOper.getDeviceName() +
                    lifterCodeDBService.getCodeDescByError(forkLifterOper.getErrorCode()));
        }
    }

    protected void process() {
        try {
            checkLifter();

            switch (status) {
                case INIT:
                    if (lockStart()) {
                        status = LifterTaskStatus.SEND_SEGMENT;
                    }
                    break;
                case SEND_SEGMENT:
                    if (lifterEvent.isTaskEnd(forkLifterOper, taskId)) {
                        status = LifterTaskStatus.END;
                        break;
                    }
                    if (canSendSegment(curSegmentInfo)) {
                        curSegmentInfo = lifterEvent.sendSegment(forkLifterOper, taskId);
                        freshTaskProcess(forkLifterOper, taskId, curSegmentInfo.getProcess());
                        status = LifterTaskStatus.WAIT_SEGMENT_END;
                    } else {
                        break;
                    }
                case WAIT_SEGMENT_END:
                    forkLifterOper.setTaskDes("等待指令" + curSegmentInfo.getCmdNo() + "完成");
                    if (lifterEvent.isTaskCmdEnd(forkLifterOper, taskId, curSegmentInfo.getCmdNo())) {
                        if (curSegmentInfo.getProcess() == LifterTaskProcess.PUT_CARGO) {
                            if (!forkLifterOper.isTwoCarDownOk()) {
                                WcsUtil.notification("放货完成，托盘未下降");
                                throw new RuntimeException("放货完成，托盘状态未下降");
                            }
                        } else if (curSegmentInfo.getProcess() == LifterTaskProcess.GET_CARGO) {
                            if (!forkLifterOper.isTwoCarUpOk() || !lifterEvent.hasCargo(forkLifterOper)) {
                                WcsUtil.notification(forkLifterOper.getDeviceName() + "任务id为" + taskId
                                        + ",取货完成，托盘未顶升，或两向车未检测到货物，光电检测："
                                        + lifterEvent.hasCargo(forkLifterOper));
                            }
                        }

                        forkLifterOper.clearLifterFinishTaskCmdNo();
                        checkCargoStatus(curSegmentInfo.getProcess());
                        status = LifterTaskStatus.SEND_SEGMENT;
                    }
                    break;
                case END:
                    freshTaskProcess(forkLifterOper, taskId, LifterTaskProcess.END);
                    unLockNode();
                    //任务结束
                    endThread();
                    LoggerUtil.info(forkLifterOper.getDeviceName() + "任务结束:" + taskId, this.getClass());
                    break;
                default:
                    throw new RuntimeException("提升机执行任务时状态异常:" + status);
            }
        } catch (IOException e) {
            log.error("提升机执行过程出现故障", e);
            LoggerUtil.error("提升机执行任务时通讯异常：" + taskId, this.getClass());
        }
    }

    /**
     * 检测是否能下发指令
     *
     * @param curSegmentInfo
     * @return
     */
    private boolean canSendSegment(LifterCurSegmentInfo curSegmentInfo) {
        if (curSegmentInfo == null) {
            return true;
        }
        switch (curSegmentInfo.getProcess()) {
            case GET_CARGO_TO_LIFTER:
                if (startNode.getZ() != endNode.getZ()) {
                    break;
                }
                //取货完回提升机后，起点与终点在同一层，没有前往终点层进度，需要对终点校验
            case GO_TO_TARGET_FLOOR:
                //前往目标层指令结束，前往终点是否能够下发检测
                LoggerUtil.debug(forkLifterOper.getDeviceName() + " 任务进度：" + curSegmentInfo.getProcess() +
                        ",准备前往终点, 任务Id：" + taskId, this.getClass());
                Node realNode = getRealNode(endNode);
                if (!nodeDBService.isCanPut(realNode)) {
                    //终点有货，不能去放货
                    return false;
                }
                if (lockEnd()) {
                    return true;
                } else {
                    return false;
                }
            default:
                break;
        }

        return true;
    }

    private Node getRealNode(Node node) {
        Integer realNodeZ = forkLifterOper.getGridzRelative() + node.getZ() - 1;
        return new Node(node.getX(), node.getY(), realNodeZ);
    }

    private boolean lockNode() {
        Set<Coord> nodes = new HashSet<>();
        if (startLocation != Location.INSIDE) {
            Node node = new Node(startNode.getX(), startNode.getY(), startNode.getZ() + forkLifterOper.getGridzRelative() - 1);
            nodes.add(node);
        }
        if (endLocation != Location.INSIDE) {
            Node node = new Node(endNode.getX(), endNode.getY(), endNode.getZ() + forkLifterOper.getGridzRelative() - 1);
            nodes.add(node);
        }
        LoggerUtil.info(forkLifterOper.getDeviceName() + "上锁节点：" + WcsUtil.listToString(new ArrayList<>(nodes)), this.getClass());
        return LockNodeManager.lockedNodesSafe(nodes, forkLifterOper);
    }

    /**
     * 上锁起点
     *
     * @return
     */
    private boolean lockStart() {
        Set<Coord> nodes = new HashSet<>();
        if (startLocation != Location.INSIDE) {
            Node node = new Node(startNode.getX(), startNode.getY(), startNode.getZ() + forkLifterOper.getGridzRelative() - 1);
            nodes.add(node);
        }
        LoggerUtil.info(forkLifterOper.getDeviceName() + "开始上锁起点：" + WcsUtil.listToString(new ArrayList<>(nodes)), this.getClass());
        return LockNodeManager.lockedNodesSafe(nodes, forkLifterOper);
    }

    /**
     * 上锁终点
     *
     * @return
     */
    private boolean lockEnd() {
        Set<Coord> nodes = new HashSet<>();
        if (endLocation != Location.INSIDE) {
            Node node = new Node(endNode.getX(), endNode.getY(), endNode.getZ() + forkLifterOper.getGridzRelative() - 1);
            nodes.add(node);
        }
        LoggerUtil.info(forkLifterOper.getDeviceName() + "开始上锁终点：" + WcsUtil.listToString(new ArrayList<>(nodes)), this.getClass());
        return LockNodeManager.lockedNodesSafe(nodes, forkLifterOper);
    }

    private void unLockNode() {
        Set<Coord> nodes = LockNodeManager.getLockCoordByObj(forkLifterOper);
        LockNodeManager.unlockedNodes(nodes, forkLifterOper);
        LoggerUtil.info(forkLifterOper.getDeviceName() + "解锁节点：" + WcsUtil.listToString(new ArrayList<>(nodes)), this.getClass());
    }

    private void checkCargoStatus(LifterTaskProcess process) {
        if (process == LifterTaskProcess.GET_CARGO_TO_LIFTER) {
            //两向车取货后，且两向车回到提升机内才修改货物状态
            WcsUtil.changeNodeCargoStatus(forkLifterOper, startNode, nodeDBService, false);
            Set<Coord> unLockNodes = new HashSet<>();
            unLockNodes.add(startNode);
            LockNodeManager.unlockedNodes(unLockNodes, forkLifterOper);
            LoggerUtil.info(ModuleType.FORK_LIFTER, LoggerType.TASK, null, forkLifterOper.getDeviceName(), "货物状态修改为无货:" + startNode, this.getClass());
            try {
                DeviceBase deviceBase = Plc.deviceBaseCoordMap.get(startNode);
                //TODO 输送线要清除任务编号
//                if (deviceBase != null) {
//                    deviceBase.setTask(0);
//                }
            } catch (Exception e) {
                WcsUtil.exceptionLog(e, this.getClass());
                LoggerUtil.warn("清除工位编号失败：" + startNode, this.getClass());
            }
        } else if (process == LifterTaskProcess.PUT_CARGO) {
            WcsUtil.changeNodeCargoStatus(forkLifterOper, endNode, nodeDBService, true);
        }
    }

    public static void freshTaskProcess(ForkLifterOper forkLifterOper, int taskId, LifterTaskProcess process) {
        forkLifterOper.setTaskId(taskId);
        forkLifterOper.setTaskProcess(process);
        LoggerUtil.info("任务" + taskId + " 提升机任务进度更新为：" + process, LifterTaskThread.class);
    }
}
